// Package common @Title http请求
package common

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"go-beego-api/component/ck_log"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"go.uber.org/zap"
)

// Request 发送请求
// ctx 上下文
// method 请求方式,目前只支持GET、POST
// url 请求地址
// req 请求参数,格式[参数名:值]
// headers 请求头,格式[请求头名:值],POST方式默认[Content-Type:application/json]
func Request(ctx context.Context, method, url string, req map[string]interface{}, headers map[string]string) (res []byte, err error) {
	if ctx == nil {
		ctx = context.Background()
	}

	if url == "" {
		err = errors.New("请求链接为空。")
		return
	}

	if method == "" {
		method = "GET"
	}

	postData := ""
	if method == http.MethodGet { // Get参数解析
		if req != nil && len(req) > 0 {
			param := ParseParam(req)
			url += "?" + param
		}
	} else if method == http.MethodPost { // Post参数解析
		if req != nil && len(req) > 0 {
			var postD []byte
			postD, err = json.Marshal(req)
			if err != nil {
				ck_log.LogCtx(ctx).Errorw("发起post请求参数序列化失败", zap.Error(err), zap.String("url", url), zap.Any("params", req))
				err = errors.New("参数序列化失败。")
				return
			}
			postData = string(postD)
			headers["Content-Type"] = "application/json"
		}
	} else {
		err = errors.New("暂不支持的请求类型。")
		return
	}

	var body io.Reader
	if postData != "" {
		body = strings.NewReader(postData)
	}

	client := &http.Client{
		Timeout: 20 * time.Second,
	}
	request, err := http.NewRequest(method, url, body)
	if err != nil {
		ck_log.LogCtx(ctx).Errorw("获取http请求客户端出错", zap.Error(err), zap.String("url", url), zap.Any("params", req))
		err = errors.New("系统错误,获取客户端失败。")
		return
	}

	if headers != nil && len(headers) > 0 { // 增加请求头
		for key, val := range headers {
			request.Header.Set(key, val)
		}
	}

	response, err := client.Do(request)
	if err != nil {
		ck_log.LogCtx(ctx).Errorw("http请求出错", zap.Error(err), zap.String("url", url), zap.Any("params", req), zap.Any("request", request))
		err = errors.New("请求出错。")
		return
	}

	defer func() {
		if response != nil && response.Body != nil {
			_ = response.Body.Close()
		}
	}()

	res, err = ioutil.ReadAll(response.Body)
	if err != nil {
		ck_log.LogCtx(ctx).Errorw("请求完成,获取响应数据出错", zap.Error(err), zap.String("url", url), zap.Any("params", req))
		err = errors.New("获取响应数据失败。")
		return
	}
	return
}

// ParseParam Get请求方式解析参数
func ParseParam(sortArrays map[string]interface{}) string {
	var keys []string
	for k := range sortArrays {
		val := StrVal(k)
		keys = append(keys, val)
	}

	sort.Strings(keys)
	items := make([]string, 0)
	for _, k := range keys {
		if sortArrays[k] != "" {
			val := StrVal(sortArrays[k])
			items = append(items, k+"="+val)
		}
	}

	return strings.Join(items, "&")
}

// StrVal 参数解析
func StrVal(value interface{}) string {
	var key string
	if value == nil {
		return key
	}

	switch value.(type) {
	case float64:
		ft := value.(float64)
		key = strconv.FormatFloat(ft, 'f', -1, 64)
	case float32:
		ft := value.(float32)
		key = strconv.FormatFloat(float64(ft), 'f', -1, 64)
	case int:
		it := value.(int)
		key = strconv.Itoa(it)
	case uint:
		it := value.(uint)
		key = strconv.Itoa(int(it))
	case int8:
		it := value.(int8)
		key = strconv.Itoa(int(it))
	case uint8:
		it := value.(uint8)
		key = strconv.Itoa(int(it))
	case int16:
		it := value.(int16)
		key = strconv.Itoa(int(it))
	case uint16:
		it := value.(uint16)
		key = strconv.Itoa(int(it))
	case int32:
		it := value.(int32)
		key = strconv.Itoa(int(it))
	case uint32:
		it := value.(uint32)
		key = strconv.Itoa(int(it))
	case int64:
		it := value.(int64)
		key = strconv.FormatInt(it, 10)
	case uint64:
		it := value.(uint64)
		key = strconv.FormatUint(it, 10)
	case string:
		key = value.(string)
	case []byte:
		key = string(value.([]byte))
	case bool:
		if value.(bool) {
			return "true"
		}
		return "false"
	default:
		newValue, _ := json.Marshal(value)
		key = string(newValue)
	}

	return key
}

func CreatefileUploadRequest(uploadUri string, fileKeyName, localFilePath string, extraFormParams map[string]string) (*http.Request, error) {
	file, err := os.Open(localFilePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(fileKeyName, filepath.Base(localFilePath))
	if err != nil {
		return nil, err
	}
	io.Copy(part, file)
	for key, val := range extraFormParams {
		writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}
	return http.NewRequest(http.MethodPost, uploadUri, body)
}

func CreatefileUploadRequest2(uploadUri string, fileKeyName, fileName string, fileBytes []byte, extraFormParams map[string]string) (*http.Request, error) {
	body := &bytes.Buffer{}
	mpWriter := multipart.NewWriter(body)
	part, err := mpWriter.CreateFormFile(fileKeyName, fileName)
	if err != nil {
		return nil, err
	}
	buf := bytes.NewBuffer(fileBytes)
	_, err = io.Copy(part, buf)
	if err != nil {
		print(err)
	}
	for key, val := range extraFormParams {
		mpWriter.WriteField(key, val)
	}
	err = mpWriter.Close()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest(http.MethodPost, uploadUri, body)
	if err == nil {
		req.Header.Set("Content-Type", mpWriter.FormDataContentType())
	}
	return req, err
}
